namespace Sirius.Mapper.Internals
{
    using System;
    using System.Collections.Generic;
    using System.Collections.ObjectModel;
    using Collections;
    using Exceptions;

    internal class SingleSet<T> : ISingleSet<T>
        where T : new ()
    {
        private readonly IDataSource dataSource;
        private ITypeMapping<T> mapping;

        public SingleSet(IDataSource dataSource)
        {
            if(dataSource == null)
            {
                throw new ArgumentNullException("dataSource");
            }

            this.dataSource = dataSource;
            mapping = new TypeMapping<T>();
        }

        public IList<T> AsList()
        {
            var enumerable = AsEnumerable();
            return new List<T>(enumerable);
        }

        public IList<TNew> AsList<TNew>(Func<T, TNew> transformer)
        {
            if(transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            var enumerable = AsEnumerable(transformer);
            return new List<TNew>(enumerable);
        }

        public ICollection<T> AsCollection()
        {
            var enumerable = AsEnumerable();
            var collection = new Collection<T>();
            foreach (var item in enumerable)
            {
                collection.Add(item);
            }

            return collection;
        }

        public ICollection<TNew> AsCollection<TNew>(Func<T, TNew> transformer)
        {
            if (transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            var enumerable = AsEnumerable(transformer);
            var collection = new Collection<TNew>();
            foreach (var item in enumerable)
            {
                collection.Add(item);
            }

            return collection;
        }

        public IEnumerable<T> AsEnumerable()
        {
            while (dataSource.MoveNextRow())
            {
                var item = new T();
                if (mapping is TypeMapping<T>)
                {
                    (mapping as TypeMapping<T>).Fill(item, dataSource);
                }
                yield return item;
            }
        }

        public IEnumerable<TNew> AsEnumerable<TNew>(Func<T, TNew> transformer)
        {
            if (transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            IList<TNew> result = new List<TNew>();
            var enumerable = AsEnumerable();
            foreach (var item in enumerable)
            {
                result.Add(transformer(item));
            }
            return result;
        }

        public ILookup<TKey, T> AsLookup<TKey>(Func<T, TKey> keySelector)
        {
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }

            var result = new Lookup<TKey, T>();

            var enumerable = AsEnumerable();
            foreach (var item in enumerable)
            {
                var key = keySelector(item);
                CheckKey(key, result);

                result.Add(keySelector(item), item);
            }

            return result;
        }

        public ILookup<TKey, TNew> AsLookup<TKey, TNew>(Func<TNew, TKey> keySelector, Func<T, TNew> transformer)
        {
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }

            if (transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            var result = new Lookup<TKey, TNew>();

            var enumerable = AsEnumerable(transformer);
            foreach (var item in enumerable)
            {
                var key = keySelector(item);
                CheckKey(key, result);

                result.Add(keySelector(item), item);
            }

            return result;
        }

        public IDictionary<TKey, T> AsDictionary<TKey>(Func<T, TKey> keySelector)
        {
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }

            IDictionary<TKey, T> result = new Dictionary<TKey, T>();

            var enumerable = AsEnumerable();
            foreach (var item in enumerable)
            {
                var key = keySelector(item);
                CheckKey(key, result);

                result.Add(key, item);
            }

            return result;
        }

        public IDictionary<TKey, TNew> AsDictionary<TKey, TNew>(Func<TNew, TKey> keySelector, Func<T, TNew> transformer)
        {
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }

            if (transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            IDictionary<TKey, TNew> result = new Dictionary<TKey, TNew>();

            var enumerable = AsEnumerable(transformer);
            foreach (var item in enumerable)
            {
                var key = keySelector(item);
                CheckKey(key, result);

                result.Add(keySelector(item), item);
            }

            return result;
        }

        private static void CheckKey<TKey, TNew>(TKey key, IDictionary<TKey, TNew> result)
        {
            if(Equals(key, default(T)))
            {
                throw new NullKeyException();
            }

            if(result.ContainsKey(key))
            {
                throw new DuplicateKeyException(key.ToString());
            }
        }

        private static void CheckKey<TKey, TNew>(TKey key, ILookup<TKey, TNew> result)
        {
            if (Equals(key, default(T)))
            {
                throw new NullKeyException();
            }

            if (result.Contains(key))
            {
                throw new DuplicateKeyException(key.ToString());
            }
        }

        public ITree<T> AsTree<TKey>(Func<T, TKey> keySelector, Func<T, TKey> parentSelector)
        {
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }

            if (parentSelector == null)
            {
                throw new ArgumentNullException("parentSelector");
            }

            var enumerable = AsEnumerable();
            var tree = new Tree<T>();

            foreach (var item in enumerable)
            {
                var parentKey = parentSelector(item);
                var key = keySelector(item);

                if (Equals(key, default(T)))
                {
                    throw new NullKeyException();
                }

                var parent = tree.Find(parentKey, keySelector);
                if (parent != null)
                {
                    parent.Add(item);
                }
            }

            return tree;
        }

        public ITree<TNew> AsTree<TKey, TNew>(Func<TNew, TKey> keySelector, Func<TNew, TKey> parentSelector, Func<T, TNew> transformer)
        {
            if (keySelector == null)
            {
                throw new ArgumentNullException("keySelector");
            }

            if (parentSelector == null)
            {
                throw new ArgumentNullException("parentSelector");
            }

            if (transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            var enumerable = AsEnumerable(transformer);
            var tree = new Tree<TNew>();

            foreach (var item in enumerable)
            {
                var parentKey = parentSelector(item);
                var key = keySelector(item);

                if (Equals(key, default(T)))
                {
                    throw new NullKeyException();
                }

                var parent = tree.Find(parentKey, keySelector);
                if (parent != null)
                {
                    parent.Add(item);
                }
            }

            return tree;
        }

        public T AsSingle()
        {
            if (dataSource != null && dataSource.MoveNextRow())
            {
                var item = new T();
                if (mapping is TypeMapping<T>)
                {
                    (mapping as TypeMapping<T>).Fill(item, dataSource);
                }

                return item;
            }

            return default(T);
        }

        public TNew AsSingle<TNew>(Func<T, TNew> transformer)
        {
            if (transformer == null)
            {
                throw new ArgumentNullException("transformer");
            }

            if (dataSource.MoveNextRow())
            {
                var item = new T();
                if (mapping is TypeMapping<T>)
                {
                    (mapping as TypeMapping<T>).Fill(item, dataSource);
                }

                return transformer(item);
            }

            return default( TNew );
        }

        public IInlineMapping<T> UsingMapping()
        {
            return new InlineMapping<T>(mapping, this);
        }

        public ISingleSet<T> UsingMapping(ITypeMapping<T> typeMapping)
        {
            if (typeMapping == null)
            {
                throw new ArgumentNullException("typeMapping");
            }

            mapping = typeMapping;
            
            return this;
        }
    }
}